#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# NetworkManager support
# Copyright (c) 2011 Paulo Matias
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

import dbus

# Connection uuid to be used.
_uuid = 'c04d68ea-9684-577c-bc55-0f24a504a3b9'

# Default settings for our connection.
_conn_name = 'USPNet'
_default_ssid = 'USPNet'

def _ssid_str(a):
    return ''.join(map(chr, a))
def _ssid_array(s):
    return dbus.Array(map(ord, s), signature=dbus.Signature('y'))

class NetworkManager():
    def __init__(self):
        self._bus = dbus.SystemBus()
        self._dev = self._find_device()
        if self._dev == None:
            raise Exception, 'Unable to find a wireless device'
        self._conn = self._find_connection()
        if self._conn == None:
            self._conn = self._create_connection()
        if self._conn == None:
            raise Exception, 'Unable to get a NetworkManager connection configuration'
        
    def _ap_tuple(self, path):
        svc = 'org.freedesktop.NetworkManager'
        ap_ifc = 'org.freedesktop.NetworkManager.AccessPoint'
        proxy = self._bus.get_object(svc, path)
        ifc = dbus.Interface(proxy, 'org.freedesktop.DBus.Properties')
        ssid = _ssid_str(ifc.Get(ap_ifc, 'Ssid'))
        bssid = str(ifc.Get(ap_ifc, 'HwAddress'))
        strength = int(ifc.Get(ap_ifc, 'Strength'))
        return (ssid, bssid, strength)
        
    def scan(self):
        svc = 'org.freedesktop.NetworkManager'
        proxy = self._bus.get_object(svc, self._dev)
        ifc = dbus.Interface(proxy, 'org.freedesktop.NetworkManager.Device.Wireless')
        for path in ifc.GetAccessPoints():
            yield self._ap_tuple(path)
            
    def connect(self, ssid, bssid=None):
        svc = 'org.freedesktop.NetworkManagerUserSettings'
        proxy = self._bus.get_object(svc, self._conn)
        ifc = dbus.Interface(proxy, 'org.freedesktop.NetworkManagerSettings.Connection')
        settings = ifc.GetSettings()
        settings['802-11-wireless']['ssid'] = _ssid_array(ssid)
        if bssid != None:
            settings['802-11-wireless']['seen-bssids'] = dbus.Array([bssid], signature=dbus.Signature('s'))
        ifc.Update(settings)
        svc = 'org.freedesktop.NetworkManager'
        proxy = self._bus.get_object(svc, '/org/freedesktop/NetworkManager')
        ifc = dbus.Interface(proxy, 'org.freedesktop.NetworkManager')
        ifc.ActivateConnection('org.freedesktop.NetworkManagerUserSettings', self._conn, self._dev, '/')
        
    def current_ap(self):
        svc = 'org.freedesktop.NetworkManager'
        proxy = self._bus.get_object(svc, self._dev)
        ifc = dbus.Interface(proxy, 'org.freedesktop.DBus.Properties')
        path = ifc.Get('org.freedesktop.NetworkManager.Device.Wireless', 'ActiveAccessPoint')
        if path == '/':
            return None
        return self._ap_tuple(path)
        
    def _find_device(self):
        svc = 'org.freedesktop.NetworkManager'
        proxy = self._bus.get_object(svc, '/org/freedesktop/NetworkManager')
        ifc = dbus.Interface(proxy, 'org.freedesktop.NetworkManager')
        for path in ifc.GetDevices():
            proxy = self._bus.get_object(svc, path)
            ifc = dbus.Interface(proxy, 'org.freedesktop.DBus.Properties')
            if ifc.Get('org.freedesktop.NetworkManager.Device', 'DeviceType') == 2:
                return path
        return None
        
    def _find_connection(self):
        svc = 'org.freedesktop.NetworkManagerUserSettings'
        proxy = self._bus.get_object(svc, '/org/freedesktop/NetworkManagerSettings')
        ifc = dbus.Interface(proxy, 'org.freedesktop.NetworkManagerSettings')
        for path in ifc.ListConnections():
            proxy = self._bus.get_object(svc, path)
            ifc = dbus.Interface(proxy, 'org.freedesktop.NetworkManagerSettings.Connection')
            if str(ifc.GetSettings()['connection']['uuid']) == _uuid:
                return path
        return None
    
    def _create_connection(self):
        conn = dbus.Dictionary({
            '802-11-wireless': dbus.Dictionary({
                'ssid': _ssid_array(_default_ssid),
                'mode': 'infrastructure',
                'seen-bssids': dbus.Array([], signature=dbus.Signature('s'))
            }, signature=dbus.Signature('sv')),
            'connection': dbus.Dictionary({
                'type': '802-11-wireless',
                'uuid': _uuid,
                'id': _conn_name
            }, signature=dbus.Signature('sv'))
        }, signature=dbus.Signature('sa{sv}'))
        svc = 'org.freedesktop.NetworkManagerUserSettings'
        proxy = self._bus.get_object(svc, '/org/freedesktop/NetworkManagerSettings')
        ifc = dbus.Interface(proxy, 'org.freedesktop.NetworkManagerSettings')
        try:
            ifc.AddConnection(conn)
        except:
            # bypass nm-applet, create directly via gconf
            self._create_connection_gconf()
        return self._find_connection()
    
    def _create_connection_gconf(self):
        import gconf
        path = '/system/networking/connections/%d'
        conf = gconf.client_get_default()
        i = 1
        while conf.dir_exists(path % i): i += 1
        path %= i
        cs = gconf.ChangeSet()
        cs.set_string(path+'/802-11-wireless/name', '802-11-wireless')
        cs.set_string(path+'/802-11-wireless/mode', 'infrastructure')
        cs.set_list  (path+'/802-11-wireless/seen-bssids', gconf.VALUE_STRING, [])
        cs.set_string(path+'/connection/name', 'connection')
        cs.set_string(path+'/connection/type', '802-11-wireless')
        cs.set_string(path+'/connection/uuid', _uuid)
        cs.set_string(path+'/connection/id', _conn_name)
        conf.commit_change_set(cs, True)
        # workaround: integer lists don't work well with ChangeSets
        conf.set_list(path+'/802-11-wireless/ssid', gconf.VALUE_INT, map(ord,_default_ssid))
        
if __name__ == '__main__':
    nm = NetworkManager()
    print repr(list(nm.scan()))
    print repr(nm.current_ap())